// Make the initial selection
if (this._appIcons.length == 1) {
- if (!backward && this._appIcons[0].windows.length > 1) {
+ if (!backward && this._appIcons[0].cachedWindows.length > 1) {
// For compatibility with the multi-app case below
this._select(0, 1);
} else
} else if (backward) {
this._select(this._appIcons.length - 1);
} else {
- if (this._appIcons[0].windows.length > 1) {
- let curAppNextWindow = this._appIcons[0].windows[1];
- let nextAppWindow = this._appIcons[1].windows[0];
+ let firstWindows = this._appIcons[0].cachedWindows;
+ if (firstWindows.length > 1) {
+ let curAppNextWindow = firstWindows[1];
+ let nextAppWindow = this._appIcons[1].cachedWindows[0];
// If the next window of the current app is more-recently-used
// than the first window of the next app, then select it.
_nextWindow : function() {
return mod(this._currentWindows[this._currentApp] + 1,
- this._appIcons[this._currentApp].windows.length);
+ this._appIcons[this._currentApp].cachedWindows.length);
},
_previousWindow : function() {
return mod(this._currentWindows[this._currentApp] - 1,
- this._appIcons[this._currentApp].windows.length);
+ this._appIcons[this._currentApp].cachedWindows.length);
},
_keyPressEvent : function(actor, event) {
},
_appActivated : function(appSwitcher, n) {
- Main.activateWindow(this._appIcons[n].windows[this._currentWindows[n]]);
+ Main.activateWindow(this._appIcons[n].cachedWindows[this._currentWindows[n]]);
this.destroy();
},
},
_windowActivated : function(thumbnailList, n) {
- Main.activateWindow(this._appIcons[this._currentApp].windows[n]);
+ Main.activateWindow(this._appIcons[this._currentApp].cachedWindows[n]);
this.destroy();
},
_finish : function() {
let app = this._appIcons[this._currentApp];
- let window = app.windows[this._currentWindows[this._currentApp]];
+ let window = app.cachedWindows[this._currentWindows[this._currentApp]];
Main.activateWindow(window);
this.destroy();
},
this._appSwitcher.showArrow(app);
} else {
this._appSwitcher.highlight(app);
- if (this._appIcons[this._currentApp].windows.length > 1)
+ if (this._appIcons[this._currentApp].cachedWindows.length > 1)
this._appSwitcher.showArrow(app);
}
this._createThumbnails();
this._currentWindows[this._currentApp] = window;
this._thumbnails.highlight(window);
- } else if (this._appIcons[this._currentApp].windows.length > 1 &&
+ } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
!noTimeout) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
THUMBNAIL_POPUP_TIME,
},
_createThumbnails : function() {
- this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].windows);
+ this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
this._thumbnails.connect('item-hovered', Lang.bind(this, this._windowHovered));
let workspaceIcons = [];
let otherIcons = [];
for (let i = 0; i < apps.length; i++) {
- let appIcon = new AppIcon.AppIcon({ appInfo: apps[i],
+ let appIcon = new AppIcon.AppIcon({ app: apps[i],
size: POPUP_APPICON_SIZE });
+ // Cache the window list now; we don't handle dynamic changes here,
+ // and we don't want to be continually retrieving it
+ appIcon.cachedWindows = appIcon.app.get_windows();
if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace))
workspaceIcons.push(appIcon);
else
},
_hasWindowsOnWorkspace: function(appIcon, workspace) {
- for (let i = 0; i < appIcon.windows.length; i++) {
- if (appIcon.windows[i].get_workspace() == workspace)
- return true;
- }
- return false;
- },
-
- _hasVisibleWindows : function(appIcon) {
- for (let i = 0; i < appIcon.windows.length; i++) {
- if (appIcon.windows[i].showing_on_its_workspace())
+ let windows = appIcon.cachedWindows;
+ for (let i = 0; i < windows.length; i++) {
+ if (windows[i].get_workspace() == workspace)
return true;
}
return false;
},
_sortAppIcon : function(appIcon1, appIcon2) {
- let vis1 = this._hasVisibleWindows(appIcon1);
- let vis2 = this._hasVisibleWindows(appIcon2);
-
- if (vis1 && !vis2) {
- return -1;
- } else if (vis2 && !vis1) {
- return 1;
- } else {
- // The app's most-recently-used window is first
- // in its list
- return (appIcon2.windows[0].get_user_time() -
- appIcon1.windows[0].get_user_time());
- }
+ return appIcon1.app.compare(appIcon2.app);
}
};
Signals.addSignalMethods(AppDisplay.prototype);
-function BaseWellItem(appInfo, isFavorite, hasMenu) {
- this._init(appInfo, isFavorite, hasMenu);
+function BaseWellItem(app, isFavorite, hasMenu) {
+ this._init(app, isFavorite, hasMenu);
}
BaseWellItem.prototype = {
__proto__: AppIcon.AppIcon.prototype,
- _init: function(appInfo, isFavorite) {
- AppIcon.AppIcon.prototype._init.call(this, { appInfo: appInfo,
+ _init: function(app, isFavorite) {
+ AppIcon.AppIcon.prototype._init.call(this, { app: app,
menuType: AppIcon.MenuType.ON_RIGHT,
glow: true });
// as say Pidgin, but ideally what we do there is have the app
// express to us that it doesn't do relaunch=new-window in the
// .desktop file.
- this.appInfo.launch();
+ this.app.get_info().launch();
},
getDragActor: function() {
}
}
-function RunningWellItem(appInfo, isFavorite) {
- this._init(appInfo, isFavorite);
+function RunningWellItem(app, isFavorite) {
+ this._init(app, isFavorite);
}
RunningWellItem.prototype = {
__proto__: BaseWellItem.prototype,
- _init: function(appInfo, isFavorite) {
- BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
+ _init: function(app, isFavorite) {
+ BaseWellItem.prototype._init.call(this, app, isFavorite);
this._dragStartX = 0;
this._dragStartY = 0;
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
- this.appInfo.launch();
+ this.app.get_info().launch();
} else {
this.activateMostRecentWindow();
}
},
activateMostRecentWindow: function () {
- // The _get_windows_for_app sorts them for us
- let mostRecentWindow = this.windows[0];
+ let mostRecentWindow = this.app.get_windows()[0];
Main.overview.activateWindow(mostRecentWindow, Main.currentTime());
},
},
menuPoppedUp: function() {
- Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.appInfo.get_id());
+ Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.app.get_id());
},
menuPoppedDown: function() {
}
};
-function InactiveWellItem(appInfo, isFavorite) {
- this._init(appInfo, isFavorite);
+function InactiveWellItem(app, isFavorite) {
+ this._init(app, isFavorite);
}
InactiveWellItem.prototype = {
__proto__: BaseWellItem.prototype,
- _init : function(appInfo, isFavorite) {
- BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
+ _init : function(app, isFavorite) {
+ BaseWellItem.prototype._init.call(this, app, isFavorite);
this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged));
this.actor.connect('activate', Lang.bind(this, this._onActivate));
},
_onActivate: function() {
- if (this.windows.length == 0) {
- this.appInfo.launch();
- Main.overview.hide();
- return true;
- }
- return false;
+ this.app.get_info().launch();
+ Main.overview.hide();
+ return true;
},
menuPoppedUp: function() {
this._redisplay();
},
- _lookupApps: function(appIds) {
- let result = [];
- for (let i = 0; i < appIds.length; i++) {
- let id = appIds[i];
- let app = this._appSystem.lookup_cached_app(id);
- if (!app)
- continue;
- result.push(app);
- }
- return result;
- },
-
- _arrayValues: function(array) {
- return array.reduce(function (values, id, index) {
- values[id] = index; return values; }, {});
+ _appIdListToHash: function(apps) {
+ let ids = {};
+ for (let i = 0; i < apps.length; i++)
+ ids[apps[i].get_id()] = apps[i];
+ return ids;
},
_onMappedNotify: function() {
this._grid.removeAll();
- let favoriteIds = this._appSystem.get_favorites();
- let favoriteIdsHash = this._arrayValues(favoriteIds);
+ let favorites = this._appMonitor.get_favorites();
+ let favoriteIds = this._appIdListToHash(favorites);
/* hardcode here pending some design about how exactly desktop contexts behave */
let contextId = "";
- let running = this._appMonitor.get_running_apps(contextId).filter(function (e) {
- return !(e.get_id() in favoriteIdsHash);
- });
- let favorites = this._lookupApps(favoriteIds);
-
- let displays = []
- this._addApps(favorites, true);
- this._addApps(running, false);
- this._displays = displays;
- },
+ let running = this._appMonitor.get_running_apps(contextId);
+ let runningIds = this._appIdListToHash(running)
- _addApps: function(apps, isFavorite) {
- for (let i = 0; i < apps.length; i++) {
- let app = apps[i];
- let windows = this._appMonitor.get_windows_for_app(app.get_id());
+ for (let i = 0; i < favorites.length; i++) {
+ let app = favorites[i];
let display;
- if (windows.length > 0)
- display = new RunningWellItem(app, isFavorite);
- else
- display = new InactiveWellItem(app, isFavorite);
+ if (app.get_windows().length > 0) {
+ display = new RunningWellItem(app, true);
+ } else {
+ display = new InactiveWellItem(app, true);
+ }
+ this._grid.actor.add_actor(display.actor);
+ }
+
+ for (let i = 0; i < running.length; i++) {
+ let app = running[i];
+ let display = new RunningWellItem(app, false);
this._grid.actor.add_actor(display.actor);
}
},
AppIcon.prototype = {
_init : function(params) {
- this.appInfo = params.appInfo;
- if (!this.appInfo)
- throw new Error('AppIcon constructor requires "appInfo" param');
+ this.app = params.app;
+ if (!this.app)
+ throw new Error('AppIcon constructor requires "app" param');
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
reactive: true });
this.actor._delegate = this;
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
- this._signalIds = [];
-
- // Note, we don't presently update the window list dynamically here; this actor
- // gets destroyed and recreated by AppWell, and in alt-tab the whole thing is
- // created each time
- this.windows = Shell.AppMonitor.get_default().get_windows_for_app(this.appInfo.get_id());
- for (let i = 0; i < this.windows.length; i++) {
- let sigId = this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
- this._signalIds.push([this.windows[i], sigId]);
- }
- this._resortWindows();
-
- this.actor.connect('destroy', Lang.bind(this, this._destroy));
- this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedChanged));
if (this._menuType != MenuType.NONE) {
this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress));
y_align: Big.BoxAlignment.CENTER,
width: this._iconSize,
height: this._iconSize });
- this.icon = this.appInfo.create_icon_texture(this._iconSize);
+ this.icon = this.app.create_icon_texture(this._iconSize);
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
font_name: "Sans 12px",
line_alignment: Pango.Alignment.CENTER,
ellipsize: Pango.EllipsizeMode.END,
- text: this.appInfo.get_name() });
+ text: this.app.get_name() });
nameBox.add_actor(this._name);
if (showGlow) {
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
- for (let i = 0; i < this.windows.length && i < 3; i++) {
+ let windows = this.app.get_windows();
+ for (let i = 0; i < windows.length && i < 3; i++) {
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
glowPath, -1, -1);
glow.keep_aspect_ratio = false;
}
},
- _destroy: function() {
- for (let i = 0; i < this._signalIds.length; i++) {
- let [obj, sigId] = this._signalIds[i];
- obj.disconnect(sigId);
- }
- },
-
- _onMappedChanged: function() {
- let mapped = this.actor.mapped;
- if (mapped && this._windowSortStale)
- this._resortWindows();
- },
-
- _resortWindows: function() {
- let mapped = this.actor.mapped;
- if (!mapped) {
- this._windowSortStale = true;
- return;
- }
- this._windowSortStale = false;
- this.windows.sort(function (a, b) {
- let activeWorkspace = global.screen.get_active_workspace();
- let wsA = a.get_workspace() == activeWorkspace;
- let wsB = b.get_workspace() == activeWorkspace;
-
- if (wsA && !wsB)
- return -1;
- else if (wsB && !wsA)
- return 1;
-
- let visA = a.showing_on_its_workspace();
- let visB = b.showing_on_its_workspace();
-
- if (visA && !visB)
- return -1;
- else if (visB && !visA)
- return 1;
- else
- return b.get_user_time() - a.get_user_time();
- });
- },
-
// AppIcon itself is not a draggable, but if you want to make
// a subclass of it draggable, you can use this method to create
// a drag actor
createDragActor: function() {
- return this.appInfo.create_icon_texture(this._iconSize);
+ return this.app.create_icon_texture(this._iconSize);
},
setHighlight: function(highlight) {
_redisplay: function() {
this._windowContainer.remove_all();
- let windows = this._source.windows;
+ let windows = this._source.app.get_windows();
this._windowContainer.show();
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
let favorites = Shell.AppSystem.get_default().get_favorites();
- let id = this._source.appInfo.get_id();
+ let id = this._source.app.get_id();
this._isFavorite = false;
for (let i = 0; i < favorites.length; i++) {
if (id == favorites[i]) {
let metaWindow = child._window;
this.emit('activate-window', metaWindow);
} else if (child == this._newWindowMenuItem) {
- this._source.appInfo.launch();
+ this._source.app.get_info().launch();
this.emit('activate-window', null);
} else if (child == this._toggleFavoriteMenuItem) {
let appSys = Shell.AppSystem.get_default();
if (this._isFavorite)
- appSys.remove_favorite(this._source.appInfo.get_id());
+ appSys.remove_favorite(this._source.app.get_id());
else
- appSys.add_favorite(this._source.appInfo.get_id());
+ appSys.add_favorite(this._source.app.get_id());
}
this.popdown();
},
this._iconBox.hide();
this._label.text = '';
if (this._focusedApp != null) {
- let icon = focusedApp.create_icon_texture(PANEL_ICON_SIZE);
+ let info = focusedApp.get_info();
+ let icon = info.create_icon_texture(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
this._iconBox.show();
- this._label.text = focusedApp.get_name();
+ this._label.text = info.get_name();
} else if (this._activeSequence != null) {
let icon = this._activeSequence.create_icon(PANEL_ICON_SIZE);
this._iconBox.append(icon, Big.BoxPackFlags.NONE);
_createWindowIcon: function(window) {
let appSys = Shell.AppSystem.get_default();
let appMon = Shell.AppMonitor.get_default()
- let appInfo = appMon.get_window_app(window.metaWindow);
+ let appInfo = appMon.get_window_app(window.metaWindow).get_info();
let iconTexture = null;
// The design is application based, so prefer the application
// icon here if we have it. FIXME - should move this fallback code
libgnome_shell_la_SOURCES = \
$(shell_built_sources) \
gnome-shell-plugin.c \
+ shell-app.c \
+ shell-app.h \
shell-app-monitor.c \
shell-app-monitor.h \
shell-app-system.c \
long watch_start_time;
MetaWindow *watched_window;
- /* <MetaWindow * window, ShellAppInfo *app> */
+ /* <MetaWindow * window, ShellApp *app> */
GHashTable *window_to_app;
+ /* <const char *, ShellApp *app> */
+ GHashTable *running_apps;
+
/* <char *context, GHashTable<char *appid, AppUsage *usage>> */
GHashTable *app_usages_for_context;
};
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- _shell_marshal_VOID__BOXED,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
- SHELL_TYPE_APP_INFO);
+ SHELL_TYPE_APP);
signals[APP_REMOVED] = g_signal_new ("app-removed",
SHELL_TYPE_APP_MONITOR,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- _shell_marshal_VOID__BOXED,
+ g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
- SHELL_TYPE_APP_INFO);
+ SHELL_TYPE_APP);
signals[WINDOW_ADDED] = g_signal_new ("window-added",
SHELL_TYPE_APP_MONITOR,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- _shell_marshal_VOID__BOXED_OBJECT,
+ _shell_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE, 2,
- SHELL_TYPE_APP_INFO,
+ SHELL_TYPE_APP,
META_TYPE_WINDOW);
signals[WINDOW_REMOVED] = g_signal_new ("window-removed",
SHELL_TYPE_APP_MONITOR,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
- _shell_marshal_VOID__BOXED_OBJECT,
+ _shell_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE, 2,
- SHELL_TYPE_APP_INFO,
+ SHELL_TYPE_APP,
META_TYPE_WINDOW);
signals[STARTUP_SEQUENCE_CHANGED] = g_signal_new ("startup-sequence-changed",
return TRUE;
}
-static ShellAppInfo *
-create_transient_app_for_window (MetaWindow *window)
-{
- return shell_app_system_create_from_window (shell_app_system_get_default (), window);
-}
-
/**
* get_app_for_window_direct:
*
* an application based on WM_CLASS. If that fails, then
* a "transient" application is created.
*
- * Return value: (transfer full): A newly-referenced #ShellAppInfo
+ * Return value: (transfer full): A newly-referenced #ShellApp
*/
-static ShellAppInfo *
+static ShellApp *
get_app_for_window_direct (MetaWindow *window)
{
- ShellAppInfo *result;
+ ShellApp *app;
+ ShellAppInfo *appinfo;
ShellAppSystem *appsys;
char *wmclass;
char *with_desktop;
wmclass = get_cleaned_wmclass_for_window (window);
if (!wmclass)
- return create_transient_app_for_window (window);
+ return _shell_app_new_for_window (window);
with_desktop = g_strjoin (NULL, wmclass, ".desktop", NULL);
g_free (wmclass);
appsys = shell_app_system_get_default ();
- result = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
+ appinfo = shell_app_system_lookup_heuristic_basename (appsys, with_desktop);
g_free (with_desktop);
- if (result == NULL)
+ if (appinfo == NULL)
{
const char *id = get_app_id_from_title (window);
if (id != NULL)
- result = shell_app_system_load_from_desktop_file (appsys, id, NULL);
+ appinfo = shell_app_system_load_from_desktop_file (appsys, id, NULL);
}
- if (result == NULL)
- result = create_transient_app_for_window (window);
+ if (appinfo == NULL)
+ return _shell_app_new_for_window (window);
- return result;
+ app = _shell_app_new (appinfo);
+ shell_app_info_unref (appinfo);
+ return app;
}
/**
* all available information such as the window's MetaGroup,
* and what we know about other windows.
*/
-static ShellAppInfo *
+static ShellApp *
get_app_for_window (ShellAppMonitor *monitor,
MetaWindow *window)
{
- ShellAppInfo *result;
+ ShellApp *result;
MetaWindow *source_window;
GSList *group_windows;
MetaGroup *group;
if (result != NULL)
{
- shell_app_info_ref (result);
+ g_object_ref (result);
return result;
}
return "";
}
+static const char *
+get_app_context (ShellApp *app)
+{
+ return "";
+}
+
static GHashTable *
get_usages_for_context (ShellAppMonitor *monitor,
const char *context)
get_app_usage_from_window (ShellAppMonitor *monitor,
MetaWindow *window)
{
- ShellAppInfo *app;
+ ShellApp *app;
const char *context;
app = g_hash_table_lookup (monitor->window_to_app, window);
context = get_window_context (window);
- return get_app_usage_for_context_and_id (monitor, context, shell_app_info_get_id (app));
+ return get_app_usage_for_context_and_id (monitor, context, shell_app_get_id (app));
}
static MetaWindow *
ShellAppMonitor *self)
{
ShellAppSystem *appsys;
- ShellAppInfo *current_app;
ShellAppInfo *new_app;
const char *id;
- current_app = g_hash_table_lookup (self->window_to_app, window);
- /* Can't have lost the app */
- g_assert (current_app != NULL);
-
/* Check if we now have a mapping using the window title */
id = get_app_id_from_title (window);
if (id == NULL)
track_window (ShellAppMonitor *self,
MetaWindow *window)
{
- ShellAppInfo *app;
+ ShellApp *app;
AppUsage *usage;
if (!window_is_tracked (window))
return;
usage = get_app_usage_from_window (self, window);
- usage->transient = shell_app_info_is_transient (app);
+ usage->transient = shell_app_info_is_transient (shell_app_get_info (app));
if (usage->transient)
{
g_signal_connect (window, "notify::title", G_CALLBACK (on_transient_window_title_changed), self);
}
+ _shell_app_add_window (app, window);
+
/* Keep track of the number of windows open for this app, when it
* switches between 0 and 1 we emit an app-added signal.
*/
usage->initially_seen_sequence = ++self->initially_seen_sequence;
usage->last_seen = get_time ();
if (usage->window_count == 1)
- g_signal_emit (self, signals[APP_ADDED], 0, app);
+ {
+ /* key is owned by the app */
+ g_hash_table_insert (self->running_apps, (char*)shell_app_get_id (app),
+ app);
+ g_signal_emit (self, signals[APP_ADDED], 0, app);
+ }
/* Emit window-added after app-added */
g_signal_emit (self, signals[WINDOW_ADDED], 0, app, window);
track_window (self, window);
}
-
static void
disassociate_window (ShellAppMonitor *self,
MetaWindow *window)
{
- ShellAppInfo *app;
+ ShellApp *app;
app = g_hash_table_lookup (self->window_to_app, window);
if (!app)
return;
- shell_app_info_ref (app);
+ g_object_ref (app);
if (window == self->watched_window)
self->watched_window = NULL;
g_hash_table_remove (self->window_to_app, window);
+ _shell_app_remove_window (app, window);
+
g_signal_emit (self, signals[WINDOW_REMOVED], 0, app, window);
if (usage->window_count == 0)
{
+ const char *id = shell_app_get_id (app);
+ g_hash_table_remove (self->running_apps, id);
g_signal_emit (self, signals[APP_REMOVED], 0, app);
- reset_usage (self, context, shell_app_info_get_id (app), usage);
+ reset_usage (self, context, id, usage);
}
}
else
g_hash_table_remove (self->window_to_app, window);
- shell_app_info_unref (app);
+ g_object_unref (app);
}
static void
while (g_hash_table_iter_next (&iter, &key, &value))
{
MetaWindow *window = key;
- ShellAppInfo *app = value;
- const char *id = shell_app_info_get_id (app);
+ ShellApp *app = value;
+ const char *id = shell_app_get_id (app);
if (!shell_app_monitor_is_window_usage_tracked (window))
continue;
(GDestroyNotify) g_hash_table_destroy);
self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal,
- NULL, (GDestroyNotify) shell_app_info_unref);
+ NULL, (GDestroyNotify) g_object_unref);
+
+ self->running_apps = g_hash_table_new (g_str_hash, g_str_equal);
g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL),
path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL);
gconf_client_notify_remove (self->gconf_client, self->gconf_notify);
g_object_unref (self->gconf_client);
g_object_unref (self->display);
+ g_hash_table_destroy (self->running_apps);
g_hash_table_destroy (self->app_usages_for_context);
for (i = 0; title_patterns[i].app_id; i++)
g_regex_unref (title_patterns[i].regex);
*
* Returns: Application associated with window
*/
-ShellAppInfo *
+ShellApp *
shell_app_monitor_get_window_app (ShellAppMonitor *monitor,
MetaWindow *metawin)
{
MetaWindow *transient_for;
- ShellAppInfo *info;
+ ShellApp *app;
transient_for = meta_window_get_transient_for (metawin);
if (transient_for != NULL)
metawin = transient_for;
- info = g_hash_table_lookup (monitor->window_to_app, metawin);
- if (info)
- shell_app_info_ref (info);
- return info;
-}
+ app = g_hash_table_lookup (monitor->window_to_app, metawin);
+ if (app)
+ g_object_ref (app);
-typedef struct {
- ShellAppMonitor *self;
- const char *context_id;
-} AppOpenSequenceSortData;
-
-static int
-sort_apps_by_open_sequence (gconstpointer a,
- gconstpointer b,
- gpointer datap)
-{
- AppOpenSequenceSortData *data = datap;
- ShellAppInfo *app_a = (ShellAppInfo*)a;
- ShellAppInfo *app_b = (ShellAppInfo*)b;
- const char *id_a = shell_app_info_get_id (app_a);
- const char *id_b = shell_app_info_get_id (app_b);
- AppUsage *usage_a;
- AppUsage *usage_b;
-
- usage_a = get_app_usage_for_context_and_id (data->self, data->context_id, id_a);
- usage_b = get_app_usage_for_context_and_id (data->self, data->context_id, id_b);
- if (usage_a->initially_seen_sequence == usage_b->initially_seen_sequence)
- return 0;
- if (usage_a->initially_seen_sequence < usage_b->initially_seen_sequence)
- return -1;
- return 1;
+ return app;
}
/**
* @context: Activity identifier
*
* Returns the set of applications which currently have at least one open
- * window in the given context.
+ * window in the given context. The returned list will be sorted
+ * by shell_app_compare().
*
- * Returns: (element-type ShellAppInfo) (transfer container): Active applications
+ * Returns: (element-type ShellApp) (transfer container): Active applications
*/
GSList *
shell_app_monitor_get_running_apps (ShellAppMonitor *monitor,
const char *context)
{
- GHashTableIter iter;
gpointer key, value;
GSList *ret;
- AppOpenSequenceSortData data;
- GHashTable *unique_apps;
-
- unique_apps = g_hash_table_new (g_str_hash, g_str_equal);
+ GHashTableIter iter;
- g_hash_table_iter_init (&iter, monitor->window_to_app);
+ g_hash_table_iter_init (&iter, monitor->running_apps);
ret = NULL;
while (g_hash_table_iter_next (&iter, &key, &value))
{
- MetaWindow *window = key;
- ShellAppInfo *app = value;
- const char *id;
+ ShellApp *app = value;
- if (strcmp (get_window_context (window), context) != 0)
+ if (strcmp (context, get_app_context (app)) != 0)
continue;
- if (!shell_app_monitor_is_window_usage_tracked (window))
- continue;
+ ret = g_slist_prepend (ret, app);
+ }
- id = shell_app_info_get_id (app);
+ ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare);
- if (g_hash_table_lookup (unique_apps, id))
- continue;
- g_hash_table_insert (unique_apps, (gpointer)id, (gpointer)id);
+ return ret;
+}
- ret = g_slist_prepend (ret, app);
+/**
+ * shell_app_monitor_get_app:
+ * @montior:
+ * @id: Application identifier
+ *
+ * For running applications, returns the existing instance
+ * of the running application model object. Otherwise,
+ * returns a new object.
+ *
+ * Returns: (transfer full): Application associated with id
+ */
+ShellApp *
+shell_app_monitor_get_app (ShellAppMonitor *monitor,
+ const char *id)
+{
+ ShellApp *app;
+ ShellAppInfo *info;
+
+ app = g_hash_table_lookup (monitor->running_apps, id);
+ if (app)
+ return g_object_ref (app);
+
+ info = shell_app_system_lookup_cached_app (shell_app_system_get_default(), id);
+ if (!info)
+ return NULL;
+
+ app = _shell_app_new (info);
+ shell_app_info_unref (info);
+
+ return app;
+}
+
+/**
+ * shell_app_monitor_get_favorites:
+ * @monitor:
+ *
+ * Returns: (transfer full) (element-type ShellApp): List of favorite applications
+ */
+GSList *
+shell_app_monitor_get_favorites (ShellAppMonitor *monitor)
+{
+ GSList *apps = NULL;
+ GList *favorite_ids, *iter;
+
+ favorite_ids = shell_app_system_get_favorites (shell_app_system_get_default ());
+ for (iter = favorite_ids; iter; iter = iter->next)
+ {
+ const char *id = iter->data;
+ ShellApp *app;
+
+ app = shell_app_monitor_get_app (monitor, id);
+ if (app)
+ apps = g_slist_prepend (apps, g_object_ref (app));
}
- g_hash_table_destroy (unique_apps);
+ apps = g_slist_reverse (apps);
- data.self = monitor;
- data.context_id = context;
- return g_slist_sort_with_data (ret, sort_apps_by_open_sequence, &data);
+ return apps;
}
static gboolean
#include <glib.h>
#include "window.h"
+#include "shell-app.h"
#include "shell-app-system.h"
-/*
+/*
* This object provides monitoring of system application directories (.desktop files)
* and activity-based statistics about applications usage
*/
struct _ShellAppMonitorClass
{
GObjectClass parent_class;
-
};
GType shell_app_monitor_get_type (void) G_GNUC_CONST;
ShellAppMonitor* shell_app_monitor_get_default(void);
-ShellAppInfo *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin);
+ShellApp *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin);
+
+ShellApp *shell_app_monitor_get_app (ShellAppMonitor *monitor, const char *id);
+
+GSList *shell_app_monitor_get_favorites (ShellAppMonitor *monitor);
GList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor,
const char *context,
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "shell-app.h"
+#include "shell-global.h"
+
+/**
+ * SECTION:shell-app
+ * @short_description: Object representing an application
+ *
+ * This object wraps a #ShellAppInfo, providing methods and signals
+ * primarily useful for running applications.
+ */
+struct _ShellApp
+{
+ GObject parent;
+
+ ShellAppInfo *info;
+
+ guint workspace_switch_id;
+
+ gboolean window_sort_stale;
+ GSList *windows;
+};
+
+G_DEFINE_TYPE (ShellApp, shell_app, G_TYPE_OBJECT);
+
+enum {
+ WINDOWS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint shell_app_signals[LAST_SIGNAL] = { 0 };
+
+const char *
+shell_app_get_id (ShellApp *app)
+{
+ return shell_app_info_get_id (app->info);
+}
+
+/**
+ * shell_app_create_icon_texture:
+ *
+ * Look up the icon for this application, and create a #ClutterTexture
+ * for it at the given size.
+ *
+ * Return value: (transfer none): A floating #ClutterActor
+ */
+ClutterActor *
+shell_app_create_icon_texture (ShellApp *app,
+ float size)
+{
+ return shell_app_info_create_icon_texture (app->info, size);
+}
+
+char *
+shell_app_get_name (ShellApp *app)
+{
+ return shell_app_info_get_name (app->info);
+}
+
+char *
+shell_app_get_description (ShellApp *app)
+{
+ return shell_app_info_get_description (app->info);
+}
+
+/**
+ * shell_app_get_info:
+ *
+ * Returns: (transfer none): Associated app info
+ */
+ShellAppInfo *
+shell_app_get_info (ShellApp *app)
+{
+ return app->info;
+}
+
+typedef struct {
+ ShellApp *app;
+ MetaWorkspace *active_workspace;
+} CompareWindowsData;
+
+static int
+shell_app_compare_windows (gconstpointer a,
+ gconstpointer b,
+ gpointer datap)
+{
+ MetaWindow *win_a = (gpointer)a;
+ MetaWindow *win_b = (gpointer)b;
+ CompareWindowsData *data = datap;
+ gboolean ws_a, ws_b;
+ gboolean vis_a, vis_b;
+
+ ws_a = meta_window_get_workspace (win_a) == data->active_workspace;
+ ws_b = meta_window_get_workspace (win_b) == data->active_workspace;
+
+ if (ws_a && !ws_b)
+ return -1;
+ else if (!ws_a && ws_b)
+ return 1;
+
+ vis_a = meta_window_showing_on_its_workspace (win_a);
+ vis_b = meta_window_showing_on_its_workspace (win_b);
+
+ if (vis_a && !vis_b)
+ return -1;
+ else if (!vis_a && vis_b)
+ return 1;
+
+ return meta_window_get_user_time (win_b) - meta_window_get_user_time (win_a);
+}
+
+/**
+ * shell_app_get_windows:
+ * @app:
+ *
+ * Get the toplevel, interesting windows which are associated with this
+ * application. The returned list will be sorted first by whether
+ * they're on the active workspace, then by whether they're visible,
+ * and finally by the time the user last interacted with them.
+ *
+ * Returns: (transfer none) (element-type MetaWindow): List of windows
+ */
+GSList *
+shell_app_get_windows (ShellApp *app)
+{
+ if (app->window_sort_stale)
+ {
+ CompareWindowsData data;
+ data.app = app;
+ data.active_workspace = meta_screen_get_active_workspace (shell_global_get_screen (shell_global_get ()));
+ app->windows = g_slist_sort_with_data (app->windows, shell_app_compare_windows, &data);
+ app->window_sort_stale = FALSE;
+ }
+
+ return app->windows;
+}
+
+static gboolean
+shell_app_has_visible_windows (ShellApp *app)
+{
+ GSList *iter;
+
+ for (iter = app->windows; iter; iter = iter->next)
+ {
+ MetaWindow *window = iter->data;
+
+ if (!meta_window_showing_on_its_workspace (window))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+shell_app_is_on_workspace (ShellApp *app,
+ MetaWorkspace *workspace)
+{
+ GSList *iter;
+
+ for (iter = app->windows; iter; iter = iter->next)
+ {
+ if (meta_window_get_workspace (iter->data) == workspace)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * shell_app_compare:
+ * @app:
+ * @other: A #ShellApp
+ *
+ * Compare one #ShellApp instance to another, in the following way:
+ * - If one of them has visible windows and the other does not, the one
+ * with visible windows is first.
+ * - If one has no windows at all (i.e. it's not running) and the other
+ * does, the one with windows is first.
+ * - Finally, the application which the user interacted with most recently
+ * compares earlier.
+ */
+int
+shell_app_compare (ShellApp *app,
+ ShellApp *other)
+{
+ gboolean vis_app, vis_other;
+ GSList *windows_app, *windows_other;
+
+ vis_app = shell_app_has_visible_windows (app);
+ vis_other = shell_app_has_visible_windows (other);
+
+ if (vis_app && !vis_other)
+ return -1;
+ else if (!vis_app && vis_other)
+ return 1;
+
+ if (app->windows && !other->windows)
+ return -1;
+ else if (!app->windows && other->windows)
+ return 1;
+
+ windows_app = shell_app_get_windows (app);
+ windows_other = shell_app_get_windows (other);
+
+ return meta_window_get_user_time (windows_other->data) - meta_window_get_user_time (windows_app->data);
+}
+
+ShellApp *
+_shell_app_new_for_window (MetaWindow *window)
+{
+ ShellApp *app;
+
+ app = g_object_new (SHELL_TYPE_APP, NULL);
+ app->info = shell_app_system_create_from_window (shell_app_system_get_default (), window);
+ _shell_app_add_window (app, window);
+
+ return app;
+}
+
+ShellApp *
+_shell_app_new (ShellAppInfo *info)
+{
+ ShellApp *app;
+
+ app = g_object_new (SHELL_TYPE_APP, NULL);
+ app->info = shell_app_info_ref (info);
+
+ return app;
+}
+
+static void
+shell_app_on_unmanaged (MetaWindow *window,
+ ShellApp *app)
+{
+ _shell_app_remove_window (app, window);
+}
+
+static void
+shell_app_on_ws_switch (ShellApp *self)
+{
+ self->window_sort_stale = TRUE;
+ g_signal_emit (self, shell_app_signals[WINDOWS_CHANGED], 0);
+}
+
+void
+_shell_app_add_window (ShellApp *app,
+ MetaWindow *window)
+{
+ if (g_slist_find (app->windows, window))
+ return;
+
+ app->windows = g_slist_prepend (app->windows, g_object_ref (window));
+ g_signal_connect (window, "unmanaged", G_CALLBACK(shell_app_on_unmanaged), app);
+ app->window_sort_stale = TRUE;
+
+ g_signal_emit (app, shell_app_signals[WINDOWS_CHANGED], 0);
+
+ if (app->workspace_switch_id == 0)
+ {
+ MetaScreen *screen = shell_global_get_screen (shell_global_get ());
+
+ app->workspace_switch_id =
+ g_signal_connect (screen, "workspace-switched", G_CALLBACK(shell_app_on_ws_switch), app);
+ }
+}
+
+static void
+disconnect_workspace_switch (ShellApp *app)
+{
+ MetaScreen *screen;
+
+ if (app->workspace_switch_id == 0)
+ return;
+
+ screen = shell_global_get_screen (shell_global_get ());
+ g_signal_handler_disconnect (screen, app->workspace_switch_id);
+ app->workspace_switch_id = 0;
+}
+
+void
+_shell_app_remove_window (ShellApp *app,
+ MetaWindow *window)
+{
+ g_object_unref (window);
+ app->windows = g_slist_remove (app->windows, window);
+ if (app->windows == NULL)
+ disconnect_workspace_switch (app);
+}
+
+static void
+shell_app_init (ShellApp *self)
+{
+}
+
+static void
+shell_app_dispose (GObject *object)
+{
+ ShellApp *app = SHELL_APP (object);
+
+ if (app->info)
+ {
+ shell_app_info_unref (app->info);
+ app->info = NULL;
+ }
+
+ if (app->windows)
+ {
+ g_slist_foreach (app->windows, (GFunc) g_object_unref, NULL);
+ g_slist_free (app->windows);
+ app->windows = NULL;
+ }
+
+ disconnect_workspace_switch (app);
+}
+
+static void
+shell_app_class_init(ShellAppClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = shell_app_dispose;
+
+ shell_app_signals[WINDOWS_CHANGED] = g_signal_new ("windows-changed",
+ SHELL_TYPE_APP,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_H__
+#define __SHELL_APP_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include "window.h"
+#include "shell-app-system.h"
+
+G_BEGIN_DECLS
+
+typedef struct _ShellApp ShellApp;
+typedef struct _ShellAppClass ShellAppClass;
+typedef struct _ShellAppPrivate ShellAppPrivate;
+
+#define SHELL_TYPE_APP (shell_app_get_type ())
+#define SHELL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_APP, ShellApp))
+#define SHELL_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_APP, ShellAppClass))
+#define SHELL_IS_APP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_APP))
+#define SHELL_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_APP))
+#define SHELL_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_APP, ShellAppClass))
+
+struct _ShellAppClass
+{
+ GObjectClass parent_class;
+
+};
+
+GType shell_app_get_type (void) G_GNUC_CONST;
+
+const char *shell_app_get_id (ShellApp *app);
+
+ClutterActor *shell_app_create_icon_texture (ShellApp *app, float size);
+char *shell_app_get_name (ShellApp *app);
+char *shell_app_get_description (ShellApp *app);
+
+ShellAppInfo *shell_app_get_info (ShellApp *app);
+
+GSList *shell_app_get_windows (ShellApp *app);
+
+gboolean shell_app_is_on_workspace (ShellApp *app, MetaWorkspace *workspace);
+
+int shell_app_compare (ShellApp *app, ShellApp *other);
+
+ShellApp* _shell_app_new_for_window (MetaWindow *window);
+
+ShellApp* _shell_app_new (ShellAppInfo *appinfo);
+
+void _shell_app_add_window (ShellApp *app, MetaWindow *window);
+
+void _shell_app_remove_window (ShellApp *app, MetaWindow *window);
+
+G_END_DECLS
+
+#endif /* __SHELL_APP_H__ */
VOID:OBJECT,INT,INT,INT,INT
VOID:BOXED
VOID:BOXED,OBJECT
+VOID:OBJECT,OBJECT
VOID:STRING,OBJECT,BOOLEAN